package com.github.winyiwin.matrix.dubbo.swagger2.controller;


import com.github.winyiwin.matrix.dubbo.swagger2.http.HttpMatch;
import com.github.winyiwin.matrix.dubbo.swagger2.http.ReferenceManager;
import com.github.winyiwin.matrix.dubbo.swagger2.reader.NameDiscover;
import io.swagger.annotations.Api;
import io.swagger.util.Json;
import io.swagger.util.PrimitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Map.Entry;

@Controller
@RequestMapping("${swagger.dubbo.http:h}")
@ApiIgnore
public class DubboHttpController {

    private static Logger logger = LoggerFactory.getLogger(DubboHttpController.class);

    private static final String CLUSTER_RPC = "rpc";

    @Value("${swagger.dubbo.enabled:true}")
    private boolean enabled;

    @Value("${swagger.dubbo.cluster:rpc}")
    private String cluster = CLUSTER_RPC;

    @RequestMapping(value = "/{interfaceClass}/{methodName}", produces = "application/json; charset=utf-8")
    @ResponseBody
    public Object invokeDubbo(@PathVariable("interfaceClass") String interfaceClass,
                                              @PathVariable("methodName") String methodName, HttpServletRequest request,
                                              HttpServletResponse response) throws Exception {
        return invokeDubbo(interfaceClass, methodName, null, request);
    }

    @RequestMapping(value = "/{interfaceClass}/{methodName}/{operationId}", produces = "application/json; charset=utf-8")
    @ResponseBody
    public Object invokeDubbo(@PathVariable("interfaceClass") String interfaceClass,
                                              @PathVariable("methodName") String methodName,
                                              @PathVariable("operationId") String operationId, HttpServletRequest request) throws Exception {
        if (!enabled) {
            throw new RuntimeException("未开启dubbo swagger");
        }

        Object ref = null;
        Method method = null;
        Object result = null;

        Entry<Class<?>, Object> entry = ReferenceManager.getInstance().getRef(interfaceClass);

        if (null == entry) {
            throw new RuntimeException("No Ref Service FOUND.");
        }
        ref = entry.getValue();
        HttpMatch httpMatch = new HttpMatch(entry.getKey(), ref.getClass());
        Method[] interfaceMethods = httpMatch.findInterfaceMethods(methodName);

        if (null != interfaceMethods && interfaceMethods.length > 0) {
            Method[] refMethods = httpMatch.findRefMethods(interfaceMethods, operationId,
                    request.getMethod());
            method = httpMatch.matchRefMethod(refMethods, methodName, request.getParameterMap().keySet());
        }
        if (null == method) {
            throw new RuntimeException("No Service Method FOUND.");
        }
        String[] parameterNames = NameDiscover.parameterNameDiscover.getParameterNames(method);

        logger.info("[Swagger-dubbo] Invoke by " + cluster);
        if (CLUSTER_RPC.equals(cluster)) {
            ref = ReferenceManager.getInstance().getProxy(interfaceClass);
            if (null == ref) {
                throw new RuntimeException("No Ref Proxy Service FOUND.");
            }
            method = ref.getClass().getMethod(method.getName(), method.getParameterTypes());
            if (null == method) {
                throw new RuntimeException("No Proxy Service Method FOUND.");
            }
        }
        logger.debug("[Swagger-dubbo] Invoke dubbo service method:{},parameter:{}", method, Json.pretty(request.getParameterMap()));
        if (null == parameterNames || parameterNames.length == 0) {
            result = method.invoke(ref);
        } else {
            Object[] args = new Object[parameterNames.length];
            Type[] parameterTypes = method.getGenericParameterTypes();
            Class<?>[] parameterClazz = method.getParameterTypes();

            for (int i = 0; i < parameterNames.length; i++) {
                Object suggestParameterValue = suggestParameterValue(parameterTypes[i],
                        parameterClazz[i], request.getParameter(parameterNames[i]));
                args[i] = suggestParameterValue;
            }
            result = method.invoke(ref, args);
        }
        return result;
    }

    private Object suggestParameterValue(Type type, Class<?> cls, String parameter) {
        PrimitiveType fromType = PrimitiveType.fromType(type);
        if (null != fromType) {
            DefaultConversionService service = new DefaultConversionService();
            boolean actual = service.canConvert(String.class, cls);
            if (actual) {
                return service.convert(parameter, cls);
            }
        } else {
            if (null == parameter) {
                return null;
            }
            try {
                return Json.mapper().readValue(parameter, cls);
            } catch (Exception e) {
                throw new IllegalArgumentException("The parameter value [" + parameter + "] should be json of [" + cls.getName() + "] Type.", e);
            }
        }
        try {
            return Class.forName(cls.getName()).newInstance();
        } catch (Exception e) {
           logger.error("error", e);
        }
        return null;
    }

}
